﻿package idv.cjcat.emitter {
	use namespace engine;
	
	use namespace engine;
	
	import flash.display.*;
	import flash.errors.*;
	import flash.utils.*;
	import idv.cjcat.emitter.ds.*;
	
	import idv.cjcat.emitter.behaviors.*;
	import idv.cjcat.emitter.deflectors.*;
	import idv.cjcat.emitter.fields.*;
	import idv.cjcat.emitter.geom.*;
	
	/**
	 * Particle design.
	 */
	public class Particle implements IEmitterXML, IEmitterDestructible {
		
		public var active:Boolean = true;
		
		/**
		 * The display object class for the particle design.
		 */
		engine var _displayClass:Class;
		public function get displayClass():Class { return _displayClass; }
		public function set displayClass(value:Class):void {
			if (value == null) {
				_displayClass = null;
				return;
			}
			
			if (!((new value()) is DisplayObject)) throw new IllegalOperationError("This is not a subclass of the DisplayObject class.");
			_displayClass = value;
			
			if (value != _mcPool.displayClass) {
				_mcPool.displayClass = value;
				_mcPool._vec.length = 0;
				
				if ((new value()) is MovieClip) {
					var mc:MovieClip = new value();
					mc.stop();
					_mcPool._vec.push(mc);
					_mcPool._position = 0;
				}
			}
		}
		
		/**
		 * Whether reuses the dead particle by simply calling the particle's <code>gotoAndStop(0)</code> method.
		 */
		public var reuseMovieClip:Boolean = false;
		engine var _mcPool:MovieClipPool = new MovieClipPool();
		
		/**
		 * The rate of emission.
		 */
		public var rate:Number = 0.3;
		
		/**
		 * The particle's life.
		 */
		public var life:Number = 50;
		public var lifeVar:Number = 5;
		
		/**
		 * The direction of emission.
		 */
		public var direction:Vector2D = new Vector2D(0, -1);
		/**
		 * The variation of the direction of emission in degrees.
		 */
		public var directionVar:Number = 180;
		/**
		 * Whether the particle is bidirectional.
		 */
		public var bidirectional:Boolean = false;
		
		/**
		 * Particle speed.
		 */
		public var speed:Number = 4;
		public var speedVar:Number = 1;
		/**
		 * Particle angular veloctiy.
		 */
		public var spin:Number = 0;
		public var spinVar:Number = 0;
		/**
		 * Whether particles are oriented to their center velocity.
		 */
		public var oriented:Boolean = false;
		/**
		 * Orientation offset in degrees.
		 */
		public var orientationOffset:Number = 0;
		/**
		 * Velocity field for velocity overriding. If set to <code>null</code>, then the velocity overriding is ignored.
		 */
		public var velocityField:Field;
		/**
		 * Local damping coefficient.
		 * <p>
		 * Zero means no damping at all, and one means everything is completely stuck still.
		 * </p>
		 */
		public var damping:Number = 0;
		
		/**
		 * Particle mass. This property affects the effects of the <code>Emitter.impulse()</code> method, gravities, and deflectors.
		 */
		public var mass:Number = 1;
		public var massVar:Number = 0;
		
		//transform
		/**
		 * Particle rotation.
		 */
		public var rotation:Number = 0;
		public var rotationVar:Number = 180;
		/**
		 * Particle scale.
		 */
		public var scale:Number = 1;
		public var scaleVar:Number = 0.2;
		/**
		 * Initial particle scale.
		 */
		public var initScale:Number = 0;
		public var initScaleVar:Number = 0;
		/**
		 * Final particle scale.
		 */
		public var finalScale:Number = 0;
		public var finalScaleVar:Number = 0;
		/**
		 * The life range starting from birth where the particle scale is interpolated between <code>initScale</code> and <code>scale</code>.
		 */
		public var scaleGrowRange:Number = 0;
		public var scaleGrowRangeVar:Number = 0;
		/**
		 * The life range ending at death where the particle scale is interpolated between <code>scale</code> and <code>finalScale</code>.
		 */
		public var scaleDecayRange:Number = 0;
		public var scaleDecayRangeVar:Number = 0;
		/**
		 * The particle alpha.
		 */
		public var alpha:Number = 1;
		public var alphaVar:Number = 0;
		/**
		 * Initial particle alpha.
		 */
		public var initAlpha:Number = 0;
		public var initAlphaVar:Number = 0;
		/**
		 * Final particle alpha.
		 */
		public var finalAlpha:Number = 0;
		public var finalAlphaVar:Number = 0;
		/**
		 * The life range starting from birth where the particle alpha is interpolated between <code>initAlpha</code> and <code>alpha</code>.
		 */
		public var alphaGrowRange:Number = 0;
		public var alphaGrowRangeVar:Number = 0;
		/**
		 * The life range ending at death where the particle alpha is interpolated between <code>alpha</code> and <code>finalAlpha</code>.
		 */
		public var alphaDecayRange:Number = 0;
		public var alphaDecayRangeVar:Number = 0;
		
		
		//bubblemotion
		/**
		 * Whether the particles move in bubble motion.
		 */
		public var bubbleMotion:Boolean = false;
		/**
		 * Whether particles are oriented to their velocity.
		 */
		public var bubbleOriented:Boolean = false;
		/**
		 * Bubble motion amplitude.
		 */
		public var bubbleAmplitude:Number = 50;
		public var bubbleAmplitudeVar:Number = 5;
		/**
		 * Bubble motion frequency.
		 */
		public var bubbleFrequency:Number = 0.25;
		public var bubbleFrequencyVar:Number = 0;
		
		//gravities
		/** @private */
		engine var _gravities:Vector.<Field> = new Vector.<Field>();
		
		//deflectors
		/** @private */
		engine var _deflectors:Vector.<Deflector> = new Vector.<Deflector>();
		
		//behavior triggers
		/** @private */
		engine var _behaviorTriggers:Vector.<BehaviorTrigger> = new Vector.<BehaviorTrigger>();
		
		//constructor
		public function Particle(displayClass:Class = null) {
			this.id = null;
			
			this.displayClass = displayClass;
		}
		
		//gravity
		/**
		 * Adds a local gravity field.
		 * @param	gravity
		 */
		public function addGravity(gravity:Field):void {
			removeGravity(gravity);
			_gravities.push(gravity);
		}
		/**
		 * Removes a local gravity field.
		 * @param	gravity
		 */
		public function removeGravity(gravity:Field):void {
			var index:int = _gravities.indexOf(gravity);
			if (index >= 0) _gravities.splice(index, 1);
		}
		/**
		 * Removes all local gravity fields.
		 * @return The original local gravity fields.
		 */
		public function clearGravities():Vector.<Field> {
			var temp:Vector.<Field> = _gravities;
			_gravities = new Vector.<Field>();
			return temp;
		}
		
		//deflector
		/**
		 * Adds a local deflector.
		 * @param	deflector
		 */
		public function addDeflector(deflector:Deflector):void {
			removeDeflector(deflector);
			_deflectors.push(deflector);
		}
		/**
		 * Removes a local deflector.
		 * @param	deflector
		 */
		public function removeDeflector(deflector:Deflector):void {
			var index:int = _deflectors.indexOf(deflector);
			if (index >= 0) _deflectors.splice(index, 1);
		}
		/**
		 * Removes all local deflectors.
		 * @return The original local deflectors.
		 */
		public function clearDeflectors():Vector.<Deflector> {
			var temp:Vector.<Deflector> = _deflectors;
			_deflectors = new Vector.<Deflector>();
			return temp;
		}
		
		//behavior trigger
		/**
		 * Adds a local behavior trigger.
		 * @param	behaviorTrigger
		 */
		public function addBehaviorTrigger(behaviorTrigger:BehaviorTrigger):void {
			removeBehaviorTrigger(behaviorTrigger);
			_behaviorTriggers.push(behaviorTrigger);
		}
		/**
		 * Removes a local behavior trigger.
		 * @param	behaviorTrigger
		 */
		public function removeBehaviorTrigger(behaviorTrigger:BehaviorTrigger):void {
			var index:int = _behaviorTriggers.indexOf(behaviorTrigger);
			if (index >= 0) _behaviorTriggers.splice(index, 1);
		}
		/**
		 * Removes all local behavior triggers.
		 * @return The original local behavior trigger.
		 */
		public function clearBehaviorTriggers():Vector.<BehaviorTrigger> {
			var temp:Vector.<BehaviorTrigger> = _behaviorTriggers;
			_behaviorTriggers = new Vector.<BehaviorTrigger>();
			return temp;
		}
		
		public function clone():Particle {
			var particle:Particle = new Particle(displayClass);
			particle.active = active;
			particle.rate = rate;
			particle.life = life;
			particle.lifeVar = lifeVar;
			particle.bidirectional = bidirectional;
			particle.speed = speed;
			particle.speedVar = speedVar;
			particle.spin = spin;
			particle.spinVar = spinVar;
			particle.oriented = oriented;
			particle.orientationOffset = orientationOffset;
			particle.damping = damping;
			particle.mass = mass;
			particle.massVar = massVar;
			particle.rotation = rotation;
			particle.rotationVar = rotationVar;
			
			particle.scale = scale;
			particle.scaleVar = scaleVar;
			particle.initScale = initScale;
			particle.initScaleVar = initScaleVar;
			particle.finalScale = finalScale;
			particle.finalScaleVar = finalScaleVar;
			particle.scaleGrowRange = scaleGrowRange;
			particle.scaleGrowRangeVar = scaleGrowRangeVar;
			particle.scaleDecayRange = scaleDecayRange;
			particle.scaleDecayRangeVar = scaleDecayRangeVar;
			particle.alpha = alpha;
			particle.alphaVar = alphaVar;
			particle.initAlpha = initAlpha;
			particle.initAlphaVar = initAlphaVar;
			particle.finalAlpha = finalAlpha ;
			particle.finalAlphaVar = finalAlphaVar;
			particle.alphaGrowRange = alphaGrowRange;
			particle.alphaGrowRangeVar = alphaGrowRangeVar;
			particle.alphaDecayRange = alphaDecayRange;
			particle.alphaDecayRangeVar = alphaDecayRangeVar;
			
			particle.bubbleMotion = bubbleMotion;
			particle.bubbleAmplitude = bubbleAmplitude;
			particle.bubbleAmplitudeVar = bubbleAmplitudeVar;
			particle.bubbleFrequency = bubbleFrequency;
			particle.bubbleFrequencyVar = bubbleFrequencyVar;
			
			particle._gravities = _gravities.concat();
			particle._deflectors = _deflectors.concat();
			particle._behaviorTriggers = _behaviorTriggers.concat();
			
			return particle;
		}
		
		
		//IEmitterDestructible
		//------------------------------------------------------------------------------------------------
		
		engine var _destroyed:Boolean = false;
		public function get destroyed():Boolean { return _destroyed }
		
		//------------------------------------------------------------------------------------------------
		//end of IEmitterDestructible
		
		
		//XML
		//------------------------------------------------------------------------------------------------
		
		/** @private */
		engine var _id:String;
		public function get id():String { return _id; }
		public function set id(value:String):void {
			delete EmitterGlobal._particles[_id];
			if (value == null) {
				while (EmitterGlobal._particles["particle" + EmitterGlobal._particleCounter] != undefined) {
					EmitterGlobal._particleCounter++;
				}
				_id = "particle" + EmitterGlobal._particleCounter;
			} else {
				if (EmitterGlobal._particles[value] == undefined) {
					_id = value;
				} else {
					throw new IllegalOperationError("This name is already used by another particle.");
				}
			}
			EmitterGlobal._particles[_id] = this;
		}
		public function toXML():XML {
			
			var xml:XML = <particle/>;
			xml.@id = _id;
			xml.@displayClass = getQualifiedClassName(displayClass);
			xml.@reuseMovieClip = reuseMovieClip.toString();
			xml.@active = active.toString();
			
			var rateXML:XML = <rate/>;
			rateXML.@value = rate.toString();
			
			var lifeXML:XML = <life/>;
			lifeXML.@value = life.toString();
			lifeXML.@valueVar = lifeVar.toString();
			
			var directionXML:XML = <direction/>;
			directionXML.@x = direction.x.toString();
			directionXML.@y = direction.y.toString();
			directionXML.@valueVar = directionVar.toString();
			directionXML.@bidirectional = bidirectional.toString();
			
			var speedXML:XML = <speed/>;
			speedXML.@value = speed.toString();
			speedXML.@valueVar = speedVar.toString();
			
			var spinXML:XML = <spin/>;
			spinXML.@value = spin.toString();
			spinXML.@valueVar = spinVar.toString();
			
			var orientedXML:XML = <oriented/>;
			orientedXML.@value = oriented.toString();
			orientedXML.@offset = orientationOffset.toString();
			
			var dampingXML:XML = <damping/>;
			dampingXML.@value = damping.toString();
			
			var massXML:XML = <mass/>;
			massXML.@value = mass.toString();
			massXML.@valueVar = massVar.toString();
			
			var rotationXML:XML = <rotation/>;
			rotationXML.@value = rotation.toString();
			rotationXML.@valueVar = rotationVar.toString();
			
			var scaleXML:XML = <scale/>;
			scaleXML.@value = scale.toString();
			scaleXML.@valueVar = scaleVar.toString();
			scaleXML.@init = initScale.toString();
			scaleXML.@initVar = initScaleVar.toString();
			scaleXML.@final = finalScale.toString();
			scaleXML.@finalVar = finalScaleVar.toString();
			scaleXML.@growRange = scaleGrowRange.toString();
			scaleXML.@growRangeVar = scaleGrowRangeVar.toString();
			scaleXML.@decayRange = scaleDecayRange.toString();
			scaleXML.@decayRangeVar = scaleDecayRangeVar.toString();
			
			var alphaXML:XML = <alpha/>;
			alphaXML.@value = alpha.toString();
			alphaXML.@valueVar = alphaVar.toString();
			alphaXML.@init = initAlpha.toString();
			alphaXML.@initVar = initAlphaVar.toString();
			alphaXML.@final = finalAlpha.toString();
			alphaXML.@finalVar = finalAlphaVar.toString();
			alphaXML.@growRange = alphaGrowRange.toString();
			alphaXML.@growRangeVar = alphaGrowRangeVar.toString();
			alphaXML.@decayRange = alphaDecayRange.toString();
			alphaXML.@decayRangeVar = alphaDecayRangeVar.toString();
			
			var bubbleXML:XML = <bubble/>;
			bubbleXML.@bubbleMotion = bubbleMotion.toString();
			bubbleXML.@bubbleOriented = bubbleOriented.toString();
			bubbleXML.@amplitude = bubbleAmplitude.toString();
			bubbleXML.@amplitudeVar = bubbleAmplitudeVar.toString();
			bubbleXML.@frequency = bubbleFrequency.toString();
			bubbleXML.@frequencyVar = bubbleFrequencyVar.toString();
			
			xml.appendChild(rateXML);
			xml.appendChild(lifeXML);
			xml.appendChild(directionXML);
			xml.appendChild(speedXML);
			xml.appendChild(spinXML);
			xml.appendChild(orientedXML);
			xml.appendChild(dampingXML);
			xml.appendChild(massXML);
			xml.appendChild(rotationXML);
			xml.appendChild(scaleXML);
			xml.appendChild(alphaXML);
			xml.appendChild(bubbleXML);
			
			
			var gravitiesXML:XML = <gravities/>;
			for each (var g:Field in _gravities) {
				var gID:XML = <id/>;
				gID.@value = g._id;
				gravitiesXML.appendChild(gID);
			}
			xml.appendChild(gravitiesXML);
			
			var deflectorsXML:XML = <deflectors/>;
			for each (var d:Deflector in _deflectors) {
				var dID:XML = <id/>;
				dID.@value = d._id;
				deflectorsXML.appendChild(dID);
			}
			xml.appendChild(deflectorsXML);
			
			var behaviorTriggersXML:XML = <behaviorTriggers/>;
			for each (var bt:BehaviorTrigger in _behaviorTriggers) {
				var btID:XML = <id/>;
				btID.@value = bt._id;
				behaviorTriggersXML.appendChild(btID);
			}
			xml.appendChild(behaviorTriggersXML);
			
			return xml;
		}
		public function parseXML(xml:XML):void {
			try {
				this.displayClass = getDefinitionByName(xml.@displayClass) as Class;
			} catch (e:Error) {
				this.displayClass = null;
				//throw new IllegalOperationError("The class definition cannot be found.");
			}
			this.reuseMovieClip = (xml.@reuseMovieClip == "true");
			
			this.active = (xml.@active == "true");
			this.rate = parseFloat(xml.rate.@value);
			this.life = parseFloat(xml.life.@value);
			this.lifeVar = parseFloat(xml.life.@valueVar);
			this.direction.x = parseFloat(xml.direction.@x);
			this.direction.y = parseFloat(xml.direction.@y);
			this.directionVar = parseFloat(xml.direction.@valueVar);
			this.bidirectional = (xml.direction.@bidirectional == "true");
			this.speed = parseFloat(xml.speed.@value);
			this.speedVar = parseFloat(xml.speed.@valueVar);
			this.spin = parseFloat(xml.spin.@value);
			this.spinVar = parseFloat(xml.spin.@valueVar);
			this.oriented = (xml.oriented.@value == "true");
			this.orientationOffset = parseFloat(xml.oriented.@offset);
			this.damping = parseFloat(xml.damping.@value);
			this.mass = parseFloat(xml.mass.@value);
			this.massVar = parseFloat(xml.mass.@valueVar);
			this.rotation = parseFloat(xml.rotation.@value);
			this.rotationVar = parseFloat(xml.rotation.@valueVar);
			
			this.scale = parseFloat(xml.scale.@value);
			this.scaleVar = parseFloat(xml.scale.@valueVar);
			this.initScale = parseFloat(xml.scale.@init);
			this.initScaleVar = parseFloat(xml.scale.@initVar);
			this.finalScale = parseFloat(xml.scale.@final);
			this.finalScaleVar = parseFloat(xml.scale.@finalVar);
			this.scaleGrowRange = parseFloat(xml.scale.@growRange);
			this.scaleGrowRangeVar = parseFloat(xml.scale.@growRangeVar);
			this.scaleDecayRange = parseFloat(xml.scale.@decayRange);
			this.scaleDecayRangeVar = parseFloat(xml.scale.@decayRangeVar);
			this.alpha = parseFloat(xml.alpha.@value);
			this.alphaVar = parseFloat(xml.alpha.@valueVar);
			this.initAlpha = parseFloat(xml.alpha.@init);
			this.initAlphaVar = parseFloat(xml.alpha.@initVar);
			this.finalAlpha = parseFloat(xml.alpha.@final) ;
			this.finalAlphaVar = parseFloat(xml.alpha.@finalVar);
			this.alphaGrowRange = parseFloat(xml.alpha.@growRange);
			this.alphaGrowRangeVar = parseFloat(xml.alpha.@growRangeVar);
			this.alphaDecayRange = parseFloat(xml.alpha.@decayRange);
			this.alphaDecayRangeVar = parseFloat(xml.alpha.@decayRangeVar);
			
			this.bubbleMotion = (xml.bubble.@bubbleMotion == "true");
			this.bubbleOriented = (xml.bubble.@bubbleOriented == "true");
			this.bubbleAmplitude = parseFloat(xml.bubble.@amplitude);
			this.bubbleAmplitudeVar = parseFloat(xml.bubble.@amplitudeVar);
			this.bubbleFrequency = parseFloat(xml.bubble.@frequency);
			this.bubbleFrequencyVar = parseFloat(xml.bubble.@frequencyVar);
			
			var node:XML, i:int;
			this._gravities.length = 0;
			for each (node in xml.gravities.id) {
				addGravity(EmitterGlobal.getFieldByID(node.@value));
			}
			this._deflectors.length = 0;
			for each (node in xml.deflectors.id) {
				addDeflector(EmitterGlobal.getDeflectorByID(node.@value));
			}
			this._behaviorTriggers.length = 0;
			for each (node in xml.behaviorTriggers.id) {
				addBehaviorTrigger(EmitterGlobal.getBehaviorTriggerByID(node.@value));
			}
		}
		
		//------------------------------------------------------------------------------------------------
		//end of XML
	}
}